#!/usr/bin/env python
# -*- coding: utf-8 -*-

# fichero de configuración
CONFIGFILE = '/etc/serverpanel/serverpanel.conf'

import string,cgi,time,sys,os,json,hashlib,base64
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from SocketServer import ThreadingMixIn
import threading
from threading import Timer
from datetime import datetime
from urllib2 import Request, urlopen, URLError, HTTPError
import subprocess
from subprocess import Popen, PIPE

from config import Config
import handler
from handler import ServerPanelHandler
import socket

import smtplib
from email.mime.text import MIMEText

# configuración
config = Config(CONFIGFILE)
# enviamos configuración al handler HTTP
ServerPanelHandler.config = config

def log(s):
    print time.strftime(config.LOGDATETIMEFORMAT, time.localtime()), '-', s

def adminmail(subject, content):
    msg = MIMEText(content)
    msg['Subject'] = subject
    msg['From'] = 'info@secuoyas.com'
    msg['To'] = config.ADMINEMAIL
    
    s = smtplib.SMTP()
    s.connect()
    s.sendmail(msg['From'], [email.strip(' ') for email in config.ADMINEMAIL.split(',')], msg.as_string())
    s.quit()

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""


def checkConnection(host, port):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(5.0)
        s.connect((host,port))
        s.close()
    except socket.timeout:
        s.close()
        return False
    except socket.error:
        s.close()
        return False
    return True



'''
Devuelve True si el servicio indicado está levantado
'''
def isServiceUp(service):
    if service == 'apache2' and checkConnection('localhost',80):
        return True
    
    if service == 'mysql' and checkConnection('localhost', 3306):
        return True
    
    if service == 'ssh' and checkConnection('localhost', 22):
        return True
    
    return False


'''
Realiza una petición GET a este mismo servidor.
'''
def doGET(uri):
    # construimos la url con autentificación
    url = "http://localhost:%d%s" % (config.PORT, uri)
    req = Request(url, None, {'Authorization': 'Basic ' + base64.b64encode( config.USER + ':' + config.PASS )})
    # Open the url
    fu = urlopen(req, None, 60)
    # Obtain data
    result = fu.read()
    fu.close()
    
    return result


'''
Realiza una petición POST a este servidor/demonio/nodo.
'''
def doPOST(uri, data):
    # construimos la url con autentificación
    url = "http://localhost:%d%s" % (config.PORT, uri)
    req = Request(url, None, {'Authorization': 'Basic ' + base64.b64encode( config.USER + ':' + config.PASS )})
    # Open the url
    fu = urlopen(req, data, 60)
    # Obtain data
    result = fu.read()
    fu.close()
    
    return result
    

'''
Inicialización de los timers de 60 segundos y 60 minutos
'''
def initTimers():
    timer60()   # every minute
    timer3600(0) # every hour


'''
Timer de 60 segundos
'''
def timer60():
    #print "timer 60s:", datetime.now()

    # 1.- comprobamos que los servicios estén levantados
    # TODO: indicar este listado de servicios mediante config
    for service in ['apache2','mysql','ssh']:
        if not isServiceUp(service):
            log("WARNING! service %s is not running!" % service)
            adminmail(
                      '[%s] Service %s down!' % (config.HOST, service),
                      'Server: %s\nService: %s\n' % (config.HOST, service)
                      )
            
            # TODO: 1.- tratar de levantarlo
            # TODO: 2.- si #1 es negativo, enviar emails a admins
        else:
            log('Service %s is running' % service)
    
    Timer(60.0, timer60).start()
    
   
'''
Timer de 60 minutos
''' 
def timer3600(TIMER_CRON_EXEC_DAY):
    log("timer 60m")
    
    # 1.- hacemos ping a todos los nodos de la netbot
    for node in handler.getNodes():
        print "DEBUG: ", node
        try:
            nhost = node[0]
            nport = int(node[1])
            nuser = node[2]
            npass = node[3]
        except ValueError:
            continue
        
        log("PING %s..." % nhost)
        # hacemos ping al nodo enviado nuestros datos
        data = handler.doPOST( nhost, nport, nuser, npass, '/ping/', {
            'host': config.HOST,
            'port': config.PORT,
            'user': config.USER,
            'pass': config.PASS
        } )
        #print "DEBUG:", data
        
        if data == None:
            log("PING fail!")
        else:
            data = json.loads(data)
            #print "DEBUG: jsoned:", data
        
            # comprobamos versión 
            if config.AUTOUPDATE == True and data['version'] > config.VERSION:
                log("NEW VERSION FOUND! Node with new version, downloading sources...")
                # encontramos una nueva versión, actualizamos nuestro código
                for source in data['sources']:
                    filedata = handler.doGET( nhost, nport, nuser, npass, '/source/%s/' % source['filename'] )
                    # guardamos
                    f = open(sys.path[0] + '/' + source['filename'], 'wb')
                    f.write(filedata)
                    f.close()
                    
                # hacemos /etc/init.d/serverpaneld restart
                # TODO: falla el KILL!
                log("RESTARTING to new version %s..." % data['version'])
                Popen(['/etc/init.d/serverpaneld', 'restart'])
                return
    
    
            # guardamos los nodos devueltos por el nodo al que se hizo ping
            log("SAVING netbot nodes...")
            for n in data['nodes']:
                if n[0] != None and len(n[0]) > 0 and n[0] != config.HOST:
                    handler.saveNode(n[0], n[1], n[2], n[3])

    # 2.- comprobamos la hora, y si estamos en la hora establecida en el config
    #     hacemos una llamada a nuestro /cron/exec/
    #log("timer 60m: hour now = ", datetime.now().hour, "; hour config =", config.CRON_EXEC_HOUR, "; day now =", datetime.now().day, "; day thread =", TIMER_CRON_EXEC_DAY)
    log('timer 60m')
    if datetime.now().hour == config.CRON_EXEC_HOUR and datetime.now().day != TIMER_CRON_EXEC_DAY:
        # informamos al log
        log("[timer 60m] running /cron/exec/ ...")
        # sólo una ejecución al día
        TIMER_CRON_EXEC_DAY = datetime.now().day
        doGET('/cron/exec/')
    
    # volvemos a ejecutar esa función dentro de 60 minutos
    Timer(3600.0, timer3600, [TIMER_CRON_EXEC_DAY]).start()



'''
Función principal
'''
def main():
    # comprobamos que el usuario es root
    if os.geteuid() != 0:
        print 'this must be run by root user!'
        sys.exit()
    
    """
    Lanzamos un hilo para comprobar periódicamente que todos
    los servicios están levantados.
    
    Lanzamos otro hilo para que ejecute el /cron/exec/ a una
    determinada hora.
    """
    Timer(5.0, initTimers).start()
    
    # trata de arrancarlo!
    try:
        log( "listenning in port %d" % config.PORT )
        server = ThreadedHTTPServer(('', config.PORT), ServerPanelHandler)
        log( "running" )
        server.serve_forever();
    except KeyboardInterrupt:
        print '^C received, shutting down server...'
        server.socket.close()
        

if __name__ == "__main__":
    main()
